<!DOCTYPE html>



  


<html class="theme-next muse use-motion" lang="zh-Hans">
<head><meta name="generator" content="Hexo 3.8.0">
  <meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<meta name="theme-color" content="#222">









<meta http-equiv="Cache-Control" content="no-transform">
<meta http-equiv="Cache-Control" content="no-siteapp">
















  
  
  <link href="/lib/fancybox/source/jquery.fancybox.css?v=2.1.5" rel="stylesheet" type="text/css">







<link href="/lib/font-awesome/css/font-awesome.min.css?v=4.6.2" rel="stylesheet" type="text/css">

<link href="/css/main.css?v=5.1.4" rel="stylesheet" type="text/css">


  <link rel="apple-touch-icon" sizes="180x180" href="/images/apple-touch-icon-next.png?v=5.1.4">


  <link rel="icon" type="image/png" sizes="32x32" href="/images/favicon-32x32-next.png?v=5.1.4">


  <link rel="icon" type="image/png" sizes="16x16" href="/images/favicon-16x16-next.png?v=5.1.4">


  <link rel="mask-icon" href="/images/logo.svg?v=5.1.4" color="#222">





  <meta name="keywords" content="Hexo, NexT">





  <link rel="alternate" href="/atom.xml" title="renhao" type="application/atom+xml">






<meta name="description" content="JavaScript测试驱动开发 【美】Venkat Subramaniam著 毛姝雯译">
<meta property="og:type" content="article">
<meta property="og:title" content="【读书笔记】JavaScript TDD">
<meta property="og:url" content="https://huangrenhao.gitee.io/2019/10/05/read-test-tdd/index.html">
<meta property="og:site_name" content="renhao">
<meta property="og:description" content="JavaScript测试驱动开发 【美】Venkat Subramaniam著 毛姝雯译">
<meta property="og:locale" content="zh-Hans">
<meta property="og:updated_time" content="2020-09-28T02:52:34.000Z">
<meta name="twitter:card" content="summary">
<meta name="twitter:title" content="【读书笔记】JavaScript TDD">
<meta name="twitter:description" content="JavaScript测试驱动开发 【美】Venkat Subramaniam著 毛姝雯译">



<script type="text/javascript" id="hexo.configurations">
  var NexT = window.NexT || {};
  var CONFIG = {
    root: '/',
    scheme: 'Muse',
    version: '5.1.4',
    sidebar: {"position":"left","display":"post","offset":12,"b2t":false,"scrollpercent":false,"onmobile":false},
    fancybox: true,
    tabs: true,
    motion: {"enable":true,"async":false,"transition":{"post_block":"fadeIn","post_header":"slideDownIn","post_body":"slideDownIn","coll_header":"slideLeftIn","sidebar":"slideUpIn"}},
    duoshuo: {
      userId: '0',
      author: '博主'
    },
    algolia: {
      applicationID: '',
      apiKey: '',
      indexName: '',
      hits: {"per_page":10},
      labels: {"input_placeholder":"Search for Posts","hits_empty":"We didn't find any results for the search: ${query}","hits_stats":"${hits} results found in ${time} ms"}
    }
  };
</script>



  <link rel="canonical" href="https://huangrenhao.gitee.io/2019/10/05/read-test-tdd/">





  <title>【读书笔记】JavaScript TDD | renhao</title>
  








<link rel="stylesheet" href="/css/prism-tomorrow.css" type="text/css"></head>

<body itemscope="" itemtype="http://schema.org/WebPage" lang="zh-Hans">
  
  
    
  

  <div class="container sidebar-position-left page-post-detail">
    <div class="headband"></div>

    <header id="header" class="header" itemscope="" itemtype="http://schema.org/WPHeader">
      <div class="header-inner"><div class="site-brand-wrapper">
  <div class="site-meta ">
    

    <div class="custom-logo-site-title">
      <a href="/" class="brand" rel="start">
        <span class="logo-line-before"><i></i></span>
        <span class="site-title">renhao</span>
        <span class="logo-line-after"><i></i></span>
      </a>
    </div>
      
        <p class="site-subtitle"></p>
      
  </div>

  <div class="site-nav-toggle">
    <button>
      <span class="btn-bar"></span>
      <span class="btn-bar"></span>
      <span class="btn-bar"></span>
    </button>
  </div>
</div>

<nav class="site-nav">
  

  
    <ul id="menu" class="menu">
      
        
        <li class="menu-item menu-item-home">
          <a href="/" rel="section">
            
              <i class="menu-item-icon fa fa-fw fa-home"></i> <br>
            
            首页
          </a>
        </li>
      
        
        <li class="menu-item menu-item-categories">
          <a href="/categories/" rel="section">
            
              <i class="menu-item-icon fa fa-fw fa-th"></i> <br>
            
            分类
          </a>
        </li>
      
        
        <li class="menu-item menu-item-archives">
          <a href="/archives/" rel="section">
            
              <i class="menu-item-icon fa fa-fw fa-archive"></i> <br>
            
            归档
          </a>
        </li>
      

      
    </ul>
  

  
</nav>



 </div>
    </header>

    <main id="main" class="main">
      <div class="main-inner">
        <div class="content-wrap">
          <div id="content" class="content">
            

  <div id="posts" class="posts-expand">
    

  

  
  
  

  <article class="post post-type-normal" itemscope="" itemtype="http://schema.org/Article">
  
  
  
  <div class="post-block">
    <link itemprop="mainEntityOfPage" href="https://huangrenhao.gitee.io/2019/10/05/read-test-tdd/">

    <span hidden itemprop="author" itemscope="" itemtype="http://schema.org/Person">
      <meta itemprop="name" content="renhao">
      <meta itemprop="description" content="">
      <meta itemprop="image" content="/images/avatar.gif">
    </span>

    <span hidden itemprop="publisher" itemscope="" itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="renhao">
    </span>

    
      <header class="post-header">

        
        
          <h1 class="post-title" itemprop="name headline">【读书笔记】JavaScript TDD</h1>
        

        <div class="post-meta">
          <span class="post-time">
            
              <span class="post-meta-item-icon">
                <i class="fa fa-calendar-o"></i>
              </span>
              
                <span class="post-meta-item-text">发表于</span>
              
              <time title="创建于" itemprop="dateCreated datePublished" datetime="2019-10-05T14:01:29+08:00">
                2019-10-05
              </time>
            

            

            
          </span>

          
            <span class="post-category">
            
              <span class="post-meta-divider">|</span>
            
              <span class="post-meta-item-icon">
                <i class="fa fa-folder-o"></i>
              </span>
              
                <span class="post-meta-item-text">分类于</span>
              
              
                <span itemprop="about" itemscope="" itemtype="http://schema.org/Thing">
                  <a href="/categories/读书笔记/" itemprop="url" rel="index">
                    <span itemprop="name">读书笔记</span>
                  </a>
                </span>

                
                
              
            </span>
          

          
            
              <span class="post-comments-count">
                <span class="post-meta-divider">|</span>
                <span class="post-meta-item-icon">
                  <i class="fa fa-comment-o"></i>
                </span>
                <a href="/2019/10/05/read-test-tdd/#comments" itemprop="discussionUrl">
                  <span class="post-comments-count valine-comment-count" data-xid="/2019/10/05/read-test-tdd/" itemprop="commentCount"></span>
                </a>
              </span>
            
          

          
          

          

          
            <div class="post-wordcount">
              
                
                <span class="post-meta-item-icon">
                  <i class="fa fa-file-word-o"></i>
                </span>
                
                  <span class="post-meta-item-text">字数统计&#58;</span>
                
                <span title="字数统计">
                  3.7k
                </span>
              

              
                <span class="post-meta-divider">|</span>
              

              
                <span class="post-meta-item-icon">
                  <i class="fa fa-clock-o"></i>
                </span>
                
                  <span class="post-meta-item-text">阅读时长 &asymp;</span>
                
                <span title="阅读时长">
                  14
                </span>
              
            </div>
          

          

        </div>
      </header>
    

    
    
    
    <div class="post-body" itemprop="articleBody">

      
      

      
        <p>JavaScript测试驱动开发 【美】Venkat Subramaniam著 毛姝雯译<a id="more"></a></p>
<h2 id="了解自动化测试"><a href="#了解自动化测试" class="headerlink" title="了解自动化测试"></a>了解自动化测试</h2><p>自动化测试对于代码设计：促使编写模块化、高内聚、低耦合的代码，让代码易于修改，有利于降低变更成本。</p>
<blockquote>
<p>代码具有可测性的条件是必须具备高内聚、低耦合的特征。</p>
<p>低内聚：一段代码执行了多个任务（需要对其进行更多测试，基本上不可能完成）</p>
<p>高耦合：代码直接与服务相关联（很难编写确定性测试）</p>
<p>自动化测试是否可行与代码设计密切相关。</p>
</blockquote>
<p>将自动化测试极端地集中在UI层会导致<a href="http://watirmelon.com/2012/01/31/" target="_blank" rel="noopener">蛋筒冰淇淋反模式</a>（icon-cream cone）。</p>
<p>MIke Cohn在(Succeeding with Agile: Software Development Using Scrum)中提出了测试金字塔的理念：底层测试最多，高层次的端到端测试最少。（即单元测试最多，功能测试其次，端到端UI层测试最少）</p>
<p>底层测试运行速度更快，更易编写，反馈循环更短，此外有助于模块化设计，更容易隔离和定位问题。</p>
<p>实现自动化测试首先需要在编写代码前先编写测试。</p>
<blockquote>
<p>tdd 与 bdd</p>
<p>测试驱动开发和行为驱动开发：tdd 多关注功能是否被正确的实现，每个功能都具备对应的测试用例；bdd 关注整体行为是否符合规格预期，适合自顶向下的设计方式</p>
</blockquote>
<h2 id="创建自动化测试"><a href="#创建自动化测试" class="headerlink" title="创建自动化测试"></a>创建自动化测试</h2><h3 id="测试驱动设计（测试先行）"><a href="#测试驱动设计（测试先行）" class="headerlink" title="测试驱动设计（测试先行）"></a>测试驱动设计（测试先行）</h3><p>测试先行原因：代码完成后很难编写出有效的测试，因为代码的形式通常不利于编写测试，在编程开始前先写测试可以让代码更具可测试性，良好的测试能让代码更加具有模块化、高内聚、低耦合的特性，从而使得设计轻量化、易于修改、维护成本低。</p>
<p>测试工具：<code>mocha</code>。JavaScript文件中识别和执行测试的一个测试引擎，默认情况下Mocha会在test目录下寻找测试文件，只负责识别和运行测试，不包含断言库。运行测试命令比如<code>npm test</code>中实际上就是运行<code>mocha</code>。</p>
<p>断言库：<code>Chai</code>。支持3种断言风格：<code>assert</code>、<code>expect</code>、<code>should</code>。</p>
<p>测试套件：一个测试文件一般包含一个或者多个测试套件。测试套件是一组相关测试的集合，这组测试验证一个函数或者一组密切相关的函数的行为。<code>describe</code>是Mocha用来定义测试套件的一个关键词，接收两个参数：测试套件的名称和包含测试套件中所有测试的一个函数。<code>it</code>函数定义了一个个单独的测试用例，这个函数也有两个参数：测试名称和测试的实际内容。</p>
<p>三明治函数：<code>beforeEach</code>和<code>afterEach</code>。如果出现在测试套件中，<code>beforeEach</code>函数会在其他测试执行前被Mocha执行，<code>afterEach</code>会在其他测试都执行完后再运行。测试套件中的所有测试都在这两个函数之间执行。</p>
<p>3-As模式：Arrange-Act-Assert（准备-行动-断言）模式。这三个部分用空行分隔，Arrange部分设置测试时需要使用到的数据，Act部分执行被测代码，Assert部分验证执行结果。</p>
<p>编写测试需要注意：</p>
<ul>
<li>测试和测试套件应该职责单一，如果一组函数密切相关，就将它们的测试放在同一个套件中，否则将它们放在不同套件中。</li>
<li>需要注意测试代码的质量。</li>
<li>测试应该与代码相关并验证代码的正确行为（需要确保这些行为本身是正确的，需要同他人合作沟通）。</li>
</ul>
<p>正向测试：当前置条件满足时，验证代码的结果确实符合预期。</p>
<p>反向测试：当前置条件或者输入不符合要求时（边界情况、非法输入、可能导致程序出错等），代码能优雅地进行处理。</p>
<p>异常测试：代码在应该抛出异常的地方正确地抛出了异常。</p>
<p>测试需要保持FAIR，即快速（fast）、自动化（automated）、独立（isolated）和可重复（repeatable）。</p>
<ul>
<li>快速：快速反馈。耗时长的测试无法经常运行。</li>
<li>最主要的作用就是验证代码，选择自动化而非手动避免了重复操作以及手动验证结果。</li>
<li>独立测试可以以任意顺序执行，并且可以选择运行一部分测试或者所有测试，在运行一个测试时不需要先运行任何其他测试。</li>
<li>测试可以重复，无需进行手动或者耗时的设置、清理操作等就可以直接运行。</li>
</ul>
<p><code>Istanbul</code>，主要用于评估代码覆盖率。test命令中调用<code>istanbul</code>而非<code>mocha</code>，该工具会运行自动化测试<code>_mocha</code>。可以监测到是否每行代码都执行了，以及执行了多少次，同时提供了行覆盖率和分支覆盖率，可以发现没有被测试到的代码。</p>
<p>避免在一个测试中放置多个<strong>独立</strong>的断言：开发人员可能会在之前已有的测试中添加另一个断言，而不是重新编写一个新的测试，这种做法最好避免。当修改代码时，我们希望能快速得知修改带来的影响，如果将多个独立的断言放在一个测试中，<strong>那么一个断言的失败会阻止后面断言的执行</strong>（类似于编译器在第一个报错处停止类似），这样就会阻碍我们看到修改带来的影响。</p>
<p>测试是文档的一种形式：不同于传统的文档或技术规范，测试是鲜活的文档，用于验证代码的每一次修改。</p>
<p>Karma：轻量级服务器，用于在不同浏览器上管理测试的加载和运行。能够以自动监测的模式运行（只要当前目录层级下代码发生了改变，Karma就会自动重新执行测试。在测试-编程的段循环中，只要保存当前文件，Karma就会注意到这个文件的变化，然后重新执行测试），需要安装众多与Karma有关的插件才能在相应浏览器中测试。</p>
<h3 id="异步测试（异步函数的自动化测试）"><a href="#异步测试（异步函数的自动化测试）" class="headerlink" title="异步测试（异步函数的自动化测试）"></a>异步测试（异步函数的自动化测试）</h3><p>编写异步测试：Mocha编写的测试可以包含一个用来标识测试结束的参数，当一个测试退出执行时，Mocha会等待这个标识以确定测试是否真的结束，如果在接收到该标识之前断言就失败或者在指定的时间内没有接收到标识，Mocha就会报告测试失败。具体的做法就是<code>it</code>函数接受的测试内容函数包含一个<code>done</code>的参数（随意命名），就是说执行完这个测试函数Mocha不会断定测试结束，通过这个<code>done</code>参数标识测试是否结束（无论被测函数式同步还是异步）。断言写在回调函数中，在实际测试中以回调函数的方式调用。可以在测试用例<code>it</code>中通过<code>this.timeout(value)</code>声明超时值。</p>
<pre><code>describe(&#39;fetch location test&#39;,funcion(){
    it(&#39;should get lat and lon from fetchLocation&#39;, function(done){
        var onSuccess = function (location) {
            expect(location).to.have.porperty(&#39;lat&#39;);
            expect(location).to.have.property(&#39;lon&#39;);
            done();
        };
        var onError = function(err) {
            throw &#39;not expected&#39;;
        };
        this.timeout(10000);
        fetchLocation(onSuccess,onError);
    });
});
</code></pre><p>测试promise（被测试函数返回一个promise对象）：</p>
<ul>
<li><p>返回promise的测试：从测试中返回promise对象，mocha就会在宣布测试结束前等着该promise对象成为已完成或失败状态。</p>
<pre><code>describe(&#39;test promise&#39;, function() {
    it(&#39;should return correct lines count for a valid file&#39;, function (){
        var callback = function(count) {
            expect(count).to.be.eql(15);
        };
        return lineCount(&#39;src/files.js&#39;).then(callback);
    });
});
</code></pre><p>可以借用测试promise的库<code>chai-as-promised</code>，扩展chai的流式api，提供一些函数来验证promise对象的响应。</p>
<pre><code>it(&#39;should return correct lines count using eventually&#39;, function(){
    return expect(lineCount(&#39;src/files.js&#39;)).to.eventually.eql(15);
});
</code></pre></li>
<li><p>使用<code>done()</code>进行测试：与前一种方法中使用<code>done()</code>进行测试类似，不过在被测试函数调用方式中使用<code>.then()</code>调动测试函数。</p>
<pre><code>it(&#39;should return correct lines for a valid file&#39;, function(done){
    var checkCount = function(count){
        expect(count).to.be.eql(15);
        done();
    };
    lineCount(&#39;src/files.js&#39;).then(checkCount);
});
</code></pre><pre><code>it(&#39;should return correct lines count using no return&#39;,function(done){
    expect(lineCount(&#39;src/files.js&#39;)).to.eventually.eql(15).notify(done);
});
</code></pre><p><code>notify</code>通知测试完成。</p>
</li>
</ul>
<h3 id="巧妙处理依赖"><a href="#巧妙处理依赖" class="headerlink" title="巧妙处理依赖"></a>巧妙处理依赖</h3><p>解决依赖问题的第一步应该是尽可能去除依赖，如果函数中的依赖是内部的，就要通过依赖注入构建松耦合的代码，可以使用测试替身来取代真实的对象或函数。</p>
<p>编写自动化测试的第一步就是确定一个或多个不具有内部依赖的函数，这些函数应该成为自动化测试的起点。</p>
<p>测试替身：代替真正依赖的对象。代替依赖的测试替身类型有<code>fake</code>、<code>stub</code>、<code>mock</code>、<code>spy</code>。stub可用于验证状态（被测代码是否为它的依赖对象设置了正确的状态），mock可用于验证行为（被测代码是否以正确的顺序对它的依赖对象调用了正确的方法）。</p>
<ul>
<li>fake：适用于测试但不能用于生产环境的实现。</li>
<li>stub：并不是真正的实现，但被调用时可以快速返回预设数据，能用来验证依赖（stub）返回（预设的）结果后代码的行为，也能验证被测代码是否正确更新了依赖对象的状态。</li>
<li>mock：与stub类似mock也可以返回预设结果，但可对交互进行跟踪（调用的次数、顺序）与测试，帮助验证被测代码与依赖之间的交互。</li>
<li>spy：与以上三种不同spy可以代理真实的依赖，在硬编码或者选中部分进行模拟的同时spy还可以与真实的服务进行交互。</li>
</ul>
<p>依赖注入：用测试替身代替依赖的一种流行、通用技术，在依赖调用时作为参数传递。比如说某个函数需要为<code>window.location</code>属性赋值，对这个函数进行测试可以创建一个空的JSON对象并将其赋值给<code>windowStub</code>，用<code>windowStub</code>代替<code>window</code>进行测试，只要验证<code>windowStub.location</code>是否符合预期结果。</p>
<blockquote>
<p>经验测试：目前的测试都是经验测试（调用同步函数或异步函数然后验证它是否返回预期值），涉及依赖通常很难预测结果，比如某个函数返回当前某地气温，很难对其进行经验测试。</p>
<p>经验测试与交互测试的选择：如果结果是确定的、可预测的而且很容易断定，那么就是用经验测试；如果代码有复杂的依赖关系，而且依赖让代码不确定、难以预测、脆弱、耗时，使用交互测试。</p>
</blockquote>
<p>交互测试：函数测试关注的是函数的行为而不是该函数的依赖对象是否正确。比如说验证两个函数之间的调用关系</p>
<pre><code>describe(&#39;location test&#39;, function() {
   it(&#39;should register handlers with getCurrentPosition&#39;, function(done){
       var original = navigator.geolocation.getCurrentPosition;

       // 测试替身mock代替getCurrentPosition真实函数
       navigator.geolocation.getCurrentPosition = fucntion(success,error) {
           expect(success).to.be.eql(onSuccess);
           expect(error).to.be.eql(onError);
           // 如果locate函数没有调用getCurrentPosition，这个模拟函数中的断言就不会被执行
           // 测试就会超时，done函数也不会被调用(无法通知测试结束)
           done(); 
       }

       locate();
       navigator.geolocation.getCurrentPosition = original;
   }) ;
});
</code></pre><pre><code>var locate = function() {
    navigator.geolocation.getCurrentPosition(onSuccess,onError);
};
var onError = function(){}
var onSuccess = function(){}
</code></pre><p>Sinon：上例中手动对<code>getCurrentPosition</code>函数进行了替换和还原（繁重、枯燥、测试代码冗长且容易出错），Sinon，测试替代框架，可以取代手动编写测试替身。</p>
<pre><code>// 执行测试前创建Sinon的sandbox，同时在测试完成后将所有mock或stub的函数恢复原始状态
// 将这段代码放在独立于所有测试套件的外部文件中，可能有多个测试套件用到这两个函数
var sandbox;  // sandbox可以保证对已有对象的修改都能恢复，确保当前测试不会影响其他测试

beforeEach(function(){
    // sandbox变量保存Sinon沙盒对象的引用，可以获取stub,mock,spy
    sandbox = sinon.sandbox.create();
});

afterEach(function(){
    sandbox.restore();
});
</code></pre><ul>
<li><p>使用Sinon的mock测试交互</p>
<pre><code>describe(&#39;location test&#39;, function(){
    it(&#39;should register handlers with getCurrentPosition&#39;, function(){
        var getCurrentPositionMock = 
            sandbox.mock(navigator.geolocation)
            .expects(&#39;getCurrentPosition&#39;)
            .withArgs(onSuccess, onError);

        locate();

        getCurrentPositionMock.verify();
    })
})
</code></pre></li>
<li><p>使用Sinon的stub测试状态</p>
<pre><code>// 测试onError函数，调用被测函数后，该测试就会对domElement这个对象状态进行断言
// 确认该对象中包含的错误信息就是传给onError函数的消息
describe(&#39;onError test&#39;,function(){
    it(&#39;should set the error DOM element&#39;,function(){
        // domElement引用一个JSON对象，代替被测代码需要的DOM元素
        var domElement = {innerHTML: &#39;&#39;};
        // getElementById函数的stub会检查该函数被调用时传入的id是否为error，如果
        // 确实如此，则将domElement返回给调用者
        sandbox.stub(document,&#39;getElementById&#39;)
            .withArgs(&#39;error&#39;)
            .returns(domElement);

        var message = &#39;you are kidding&#39;;
        // positionError轻量级JSON对象，用于代替onError函数期望得到的PositionError实例
        var positionError = { message: message };

        onError(positionError);
        expect(domElement.innerHTML).to.be.eql(message);
    });
});
</code></pre><pre><code>// 无法获取用户当前的位置（网络等因素），getCurrentPosition就会调用onError函数
var onError = function (error) {
    document.getElementById(&#39;error&#39;).innerHTML = error.message;
}
</code></pre></li>
<li><p>使用Sinon的spy拦截调用</p>
<pre><code>describe(&#39;onSuccess test&#39;, function(){
    it(&#39;should call createURL with latitude and longitude&#39;,function(){
        // 这个spy只拦截和记录传给真正函数的参数，不会阻塞或者绕过该调用
        var createURLSpy = sandbox.spy(window, &#39;createURL&#39;);

        var position = {coords:{latitude:40.41,longitude:-105.55}};

        onSuccess(position);

        expect(createURLSpy).to.have.been.calledWith(40.41,-105.55);
    });

    it(&#39;should call setLocation with URL returned by createURL&#39;,function(){
        var url = &#39;http://www.example.com&#39;;

        sandbox.stub(window,&#39;createURL&#39;).returns(url);

        var setLocationSpy = sandbox.spy(window,&#39;setLocation&#39;);

        var position = {coords:{latitude:40.41,longitude:-105.55}};
        onSuccess(position);
        // 验证该spy是否以正确的参数被调用(真正的函数被调用了也没关系)
        expect(setLocationSpy).to.have.been.calledWith(window,url);
    })
});
</code></pre><pre><code>// onSuccess函数返回用户位置的详细数据，从传入的position参数中获取经度和纬度
// 然后调用createURL函数获取URL，然后将数据传给setLocation设置window对象的location属性
var onSuccess = function(position){
    var latitude = position.coords.latitude;
    var longitude = position.coords.longitude;

    var url = createURL(latitude,longitude);
    setLocation(window,url);
}
</code></pre></li>
</ul>
<h2 id="真实的自动化测试"><a href="#真实的自动化测试" class="headerlink" title="真实的自动化测试"></a>真实的自动化测试</h2><p>本部分书中介绍了<code>Node.js</code>、<code>Express</code>、<code>jQuery</code>、<code>AngularJS</code>，与我前端偏好设计框架<code>React</code>不符，暂未看。</p>

      
    </div>
    
    
    

    

    

    

    <footer class="post-footer">
      

      
      
      

      
        <div class="post-nav">
          <div class="post-nav-next post-nav-item">
            
              <a href="/2019/10/01/css-layout/" rel="next" title="css中几种常见布局设计">
                <i class="fa fa-chevron-left"></i> css中几种常见布局设计
              </a>
            
          </div>

          <span class="post-nav-divider"></span>

          <div class="post-nav-prev post-nav-item">
            
              <a href="/2019/10/07/nginx/" rel="prev" title="nginx教程学习">
                nginx教程学习 <i class="fa fa-chevron-right"></i>
              </a>
            
          </div>
        </div>
      

      
      
    </footer>
  </div>
  
  
  
  </article>



    <div class="post-spread">
      
    </div>
  </div>


          </div>
          


          

  
    <div class="comments" id="comments">
    </div>
  



        </div>
        
          
  
  <div class="sidebar-toggle">
    <div class="sidebar-toggle-line-wrap">
      <span class="sidebar-toggle-line sidebar-toggle-line-first"></span>
      <span class="sidebar-toggle-line sidebar-toggle-line-middle"></span>
      <span class="sidebar-toggle-line sidebar-toggle-line-last"></span>
    </div>
  </div>

  <aside id="sidebar" class="sidebar">
    
    <div class="sidebar-inner">

      

      
        <ul class="sidebar-nav motion-element">
          <li class="sidebar-nav-toc sidebar-nav-active" data-target="post-toc-wrap">
            文章目录
          </li>
          <li class="sidebar-nav-overview" data-target="site-overview-wrap">
            站点概览
          </li>
        </ul>
      

      <section class="site-overview-wrap sidebar-panel">
        <div class="site-overview">
          <div class="site-author motion-element" itemprop="author" itemscope="" itemtype="http://schema.org/Person">
            
              <p class="site-author-name" itemprop="name">renhao</p>
              <p class="site-description motion-element" itemprop="description"></p>
          </div>

          <nav class="site-state motion-element">

            
              <div class="site-state-item site-state-posts">
              
                <a href="/archives/">
              
                  <span class="site-state-item-count">94</span>
                  <span class="site-state-item-name">日志</span>
                </a>
              </div>
            

            
              
              
              <div class="site-state-item site-state-categories">
                <a href="/categories/index.html">
                  <span class="site-state-item-count">19</span>
                  <span class="site-state-item-name">分类</span>
                </a>
              </div>
            

            

          </nav>

          
            <div class="feed-link motion-element">
              <a href="/atom.xml" rel="alternate">
                <i class="fa fa-rss"></i>
                RSS
              </a>
            </div>
          

          

          
          

          
          

          

        </div>
      </section>

      
      <!--noindex-->
        <section class="post-toc-wrap motion-element sidebar-panel sidebar-panel-active">
          <div class="post-toc">

            
              
            

            
              <div class="post-toc-content"><ol class="nav"><li class="nav-item nav-level-2"><a class="nav-link" href="#了解自动化测试"><span class="nav-number">1.</span> <span class="nav-text">了解自动化测试</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#创建自动化测试"><span class="nav-number">2.</span> <span class="nav-text">创建自动化测试</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#测试驱动设计（测试先行）"><span class="nav-number">2.1.</span> <span class="nav-text">测试驱动设计（测试先行）</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#异步测试（异步函数的自动化测试）"><span class="nav-number">2.2.</span> <span class="nav-text">异步测试（异步函数的自动化测试）</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#巧妙处理依赖"><span class="nav-number">2.3.</span> <span class="nav-text">巧妙处理依赖</span></a></li></ol></li><li class="nav-item nav-level-2"><a class="nav-link" href="#真实的自动化测试"><span class="nav-number">3.</span> <span class="nav-text">真实的自动化测试</span></a></li></ol></div>
            

          </div>
        </section>
      <!--/noindex-->
      

      

    </div>
  </aside>


        
      </div>
    </main>

    <footer id="footer" class="footer">
      <div class="footer-inner">
        <div class="copyright">&copy; <span itemprop="copyrightYear">2020</span>
  <span class="with-love">
    <i class="fa fa-user"></i>
  </span>
  <span class="author" itemprop="copyrightHolder">renhao</span>

  
    <span class="post-meta-divider">|</span>
    <span class="post-meta-item-icon">
      <i class="fa fa-area-chart"></i>
    </span>
    
      <span class="post-meta-item-text">Site words total count&#58;</span>
    
    <span title="Site words total count">265.9k</span>
  
</div>


  <div class="powered-by">由 <a class="theme-link" target="_blank" href="https://hexo.io">Hexo</a> 强力驱动</div>



  <span class="post-meta-divider">|</span>



  <div class="theme-info">主题 &mdash; <a class="theme-link" target="_blank" href="https://github.com/iissnan/hexo-theme-next">NexT.Muse</a> v5.1.4</div>




        







        
      </div>
    </footer>

    
      <div class="back-to-top">
        <i class="fa fa-arrow-up"></i>
        
      </div>
    

    

  </div>

  

<script type="text/javascript">
  if (Object.prototype.toString.call(window.Promise) !== '[object Function]') {
    window.Promise = null;
  }
</script>









  












  
  
    <script type="text/javascript" src="/lib/jquery/index.js?v=2.1.3"></script>
  

  
  
    <script type="text/javascript" src="/lib/fastclick/lib/fastclick.min.js?v=1.0.6"></script>
  

  
  
    <script type="text/javascript" src="/lib/jquery_lazyload/jquery.lazyload.js?v=1.9.7"></script>
  

  
  
    <script type="text/javascript" src="/lib/velocity/velocity.min.js?v=1.2.1"></script>
  

  
  
    <script type="text/javascript" src="/lib/velocity/velocity.ui.min.js?v=1.2.1"></script>
  

  
  
    <script type="text/javascript" src="/lib/fancybox/source/jquery.fancybox.pack.js?v=2.1.5"></script>
  


  


  <script type="text/javascript" src="/js/src/utils.js?v=5.1.4"></script>

  <script type="text/javascript" src="/js/src/motion.js?v=5.1.4"></script>



  
  

  
  <script type="text/javascript" src="/js/src/scrollspy.js?v=5.1.4"></script>
<script type="text/javascript" src="/js/src/post-details.js?v=5.1.4"></script>



  


  <script type="text/javascript" src="/js/src/bootstrap.js?v=5.1.4"></script>



  


  




	





  





  










  <script src="//cdn1.lncld.net/static/js/3.0.4/av-min.js"></script>
  <script src="//unpkg.com/valine/dist/Valine.min.js"></script>
  
  <script type="text/javascript">
    var GUEST = ['nick','mail','link'];
    var guest = 'nick,mail,link';
    guest = guest.split(',').filter(item=>{
      return GUEST.indexOf(item)>-1;
    });
    new Valine({
        el: '#comments' ,
        verify: false,
        notify: false,
        appId: '2P1xqUWiCoNm14MH4yhURlJi-gzGzoHsz',
        appKey: 'uJN0uagHIVgXtBO1OuLV9Ban',
        placeholder: 'Just go go',
        avatar:'mm',
        guest_info:guest,
        pageSize:'10' || 10,
    });
  </script>



  





  

  

  

  
  

  

  

  

</body>
</html>
